//
// Created by martin on 8/6/21.
//

#include <malloc.h>
#include <string.h>
#include <event.h>
#include "error.h"
#include "task.h"
#include "ftprequest.h"
#include "socket.h"
#include "cmd/cmdaction.h"
#include "strex.h"

static int ftpcmd_parse(FtpRequest *req, const char *cmd_str);
static void ftpcmd_convert_request_to_session(Session *sess, const FtpRequest *req);

/**
 * 解析原始FTP请求字符串，并封装到FtpRequest对象req
 * @param req
 * @param cmd_str
 * @return
 * @note FTP字符串格式: 请求命令<SP>请求命令内容<CRLF>
 * i.g. USER命令: USER anonymous\r\n
 */
static int ftpcmd_parse(FtpRequest *req, const char *cmd_str)
{
    if (!req || !cmd_str) return -1;

    size_t len = strlen(cmd_str);
    if (len < 2) // 非法命令字符串
        return -1;

    // 检查是否以 \r\n 结尾
    if (!(cmd_str[len - 1] == '\n' && cmd_str[len - 2] == '\r')) // 非法命令字符串
        return -1;

    // 解析字符串
    // 将字符串按第一个空格切分为2部分，分别为命令名称，命令内容
    str_split(req->cmd_raw, req->cmd_name, req->cmd_text, " ");

    // 裁剪首尾空白字符
    str_trim_self(req->cmd_name);
    str_trim_self(req->cmd_text);
    return 0;
}

/**
 * 将ftp命令由FtpRequest转化成Session格式
 */
static void ftpcmd_convert_request_to_session(Session *sess, const FtpRequest *req)
{
    if (!sess || !req) return;

    strcpy(sess->cmd_name, req->cmd_name);
    strcpy(sess->cmd_text, req->cmd_text);
}

/**
 * 控制连接bufferevent 读取回调
 * @param bev 监听控制连接的bufferevent结构对象
 * @param arg 用户参数
 */
static void read_cb(struct bufferevent *bev, void *arg)
{
    Task *task = (Task *)arg;
    Session *sess = task->sess;

    // 创建FtpRequest对象
    FtpRequest *req = ftprequest_new();
    if (!req) {
        err_ret("create ftp request failed");
        return;
    }
    // 从buffervent读取客户端发送数据，并封装到FtpRequest对象
    size_t len = bufferevent_read(bev, req->cmd_raw, sizeof(req->cmd_raw) - 1);
    req->cmd_raw[len] = 0; // 便于打印字符串

    // 解析FTP请求字符串 命令名称及命令内容
    ftpcmd_parse(req, req->cmd_raw);
    // 将解析出来的FTP命令结果，拷贝到session中保存
    ftpcmd_convert_request_to_session(sess, req);

    printf("RequestCMD: %s %s\n", req->cmd_name, req->cmd_text);

    // 释放request对象
    free(req);

    int findCmdHandler = 0;
    int i;
    for (i = 0; cmd_action[i].name != NULL; ++i) {
        if (0 == strcasecmp(cmd_action[i].name, sess->cmd_name)) { // 命令-动作列表中找到相同命令，忽略大小写
            if (cmd_action[i].cmd_cb) {
                findCmdHandler = 1;
                cmd_action[i].cmd_cb(sess);
            }
            break;
        }
    }

    if (0 == findCmdHandler) {
        send_reply(bev, 502, "Command not implemented.");
    }
}

/**
 * 控制连接 写回调
 * @param bev 监听控制连接的bufferevent结构对象
 * @param arg 用户参数
 */
static void write_cb(struct bufferevent *bev, void *arg)
{
//    printf("task write_cb\n");
}

/**
 * 控制连接 事件回调
 * @param bev 监听控制连接的bufferevent结构对象
 * @param what 事件, 支持BEV_EVENT_EOF，BEV_EVENT_TIMEOUT，BEV_EVENT_ERROR
 * @param arg 用户参数
 * @note 连接结束或异常关闭时，释放bufferevent及Task资源
 */
static void event_cb(struct bufferevent *bev, short what, void *arg)
{
    Task *task = (Task *)arg;

    if (what & BEV_EVENT_EOF) {
        printf("Connection finished.");
        bufferevent_free(bev);
        task_free(task);
    }
    else if (what & BEV_EVENT_TIMEOUT) {
        printf("Connection timed out.");
        send_reply(bev, 421, "Connection timed out.");
        bufferevent_free(bev);
        task_free(task);
    }
    else if (what & BEV_EVENT_ERROR) {
        printf("Connection error occurs.");
        bufferevent_free(bev);
        task_free(task);
    }
}

/**
 * 新建Task对象，申请存储空间
 * @note 要使用Task对象，还需要调用task_init()对其进行初始化
 * @return 新建的Task对象
 */
Task* task_new()
{
    Task *task = (Task *)malloc(sizeof(Task));
    if (!task) {
        err_ret("Create task failure");
        return NULL;
    }

    bzero(task, sizeof(Task));
    return task;
}

/**
 * 释放Task对象，释放存储空间
 * @param task 待释放Task对象
 * @return void
 */
void  task_free(Task *task)
{
    if (!task) return;
    if (task->sess) {
        session_free(task->sess);
        task->sess = NULL;
    }
    free(task);
}

/**
 * 初始化任务
 * @param task 任务对象
 * @param base libevent上下文
 * @note 每个task对应一个客户连接，监听客户sock fd读写事件; 一个线程对应一个event_base, 监听多个客户连接.
 * 由于event_base在线程中创建，故需要由线程传入
 */
int  task_init(Task *task, struct event_base *base)
{
    if (!task || !base) return -1;
    printf("task_init\n");

    // 创建session用于保存会话信息
    Session *sess = session_new();
    if (!sess) {
        err_ret("create session failed");
        return -1;
    }
    // 保存session
    task->sess = sess;

    // 保存event_base
    task->base = base;

    /* Note: 在事件循环已经开启后，再创建bufferevent，是否会导致原来的event_base结构修改？
     *  如果不加锁(创建event_base使用选项 EVENT_BASE_FLAG_NOLOCK)，是否线程不安全？
     *  除了创建event_base是在main线程，后续添加监听事件均在同一个线程内，不存在多个线程同时修改event_base的情况
     */
    // 创建bufferevent监听用户读写事件
    struct bufferevent *bev;
    bev = bufferevent_socket_new(base, task->connfd, BEV_OPT_CLOSE_ON_FREE);
    if (!bev) {
        err_msg("bufferevent_socket_new failed");
        return -1;
    }
    task->bev = bev;
    task->sess->bev = bev;

    // 设置bufferevent回调
    bufferevent_setcb(bev, read_cb, write_cb, event_cb, task);
    // 设置bufferevent权限
    bufferevent_enable(bev, EV_READ | EV_WRITE);
    // 设置超时
    struct timeval tv = {600, 0}; // 用户连接10分钟超时
    bufferevent_set_timeouts(bev, &tv, NULL);

    // 欢迎语
    send_reply(bev, 220, "Features: p .");

    return 0;
}

/**
 * 设置当前任务对象task的connfd(连接fd)值, 供task初始化时绑定bufferevent用
 * @param task 任务对象
 * @param connfd task处理的连接fd
 */
void task_setConnfd(Task *task, int connfd)
{
    if (!task) return;
    task->connfd = connfd;
}